package jfprincesstests.systemtests;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.Map.Entry;

import jfuzzyprincepless.agents.*;
import jfuzzyprincepless.zarchive.algorithm.structure.ICommandConstruction;
import jfuzzyprincepless.zarchive.algorithm.structure.forcnstr.fuzzyfor.*;
import jfuzzyprincepless.zarchive.algorithm.structure.forcnstr.INegotiationFor;
import jfuzzyprincepless.negotiation.DefaultNegotiationContext;
import jfuzzyprincepless.negotiation.IExecutionContext;
import jfuzzyprincepless.negotiation.INegotiation;
import jfuzzyprincepless.negotiation.INegotiationContext;
import jfuzzyprincepless.negotiation.services.IService;
import jfuzzyprincepless.zarchive.variables.FuzzySet;
import jfuzzyprincepless.zarchive.variables.IFuzzySet;
import jfuzzyprincepless.zarchive.agents.BaseUtilityFuncAgent;
import jfuzzyprincepless.zarchive.agents.IUtilityFuncNegotiationAgent;
import jfuzzyprincepless.zarchive.negotiation.protocols.INegotiationProtocol__Q_del;
import jfuzzyprincepless.zarchive.negotiation.protocols.utilityfuncprotocol.UtilityFuncNegotiation;
import jfuzzyprincepless.zarchive.negotiation.protocols.propositionnetwork.ExecuteForNegotiation;
import jfuzzyprincepless.zarchive.negotiation.ZArchNegotiationUtils;
import jfuzzyprincepless.zarchive.negotiation.services.IExecuteForService;
import jfuzzyprincepless.zarchive.negotiation.services.IUtilityInfoService;

import laboratoryQ.junit.ignore.patch.BaseTestCase;

@SuppressWarnings("unused")
public class TestPremiumWithUtilityFunctionProtocol
	extends BaseTestCase
{
	private static final double EPS = 1e-5;

	/**
	 * first
	 * @see jfprincesstests.systemtests.TestPremiumAssignment#testPremiumAssignment()
	 *
	 * algorithm in this test is absolutely the same
	 * but negotiation protocol is different
	 *
	 * each agent (addEducationLevel, addTimeSpentLevel, substractReprimand) wants
	 * premium to be proportional to its parameter
	 * and agent's dis-utility is computed as distance
	 * between real situation and his ideal situation
	 */
	public void testPremiumWithUtilityFunctionProtocol() {
		PremiumsInfo premiumInfo = new PremiumsInfo();
		IFuzzySet<Employee> employeeSet = new FuzzySet<Employee>();
		employeeSet.add(new Employee("W0", 1, 0.2, 3), 0.3);
		employeeSet.add(new Employee("W1", 4, 0.5, 6), 0.4);
		employeeSet.add(new Employee("W2", 7, 0.8, 9), 0.5);
		employeeSet.add(new Employee("W3", 10, 1.1, 12), 0.6);
		employeeSet.add(new Employee("W4", 13, 1.4, 15), 0.7);
		NegotiationFuzzyFor<Employee> forPositivEmployeeLoop =
			new NegotiationFuzzyFor<Employee>(employeeSet);

		PremiumValVariable premiumValVariable =
			new PremiumValVariable();
		ICommandConstruction initialisePremiumVal =
			new InitialisePremiumVal(
				premiumValVariable,
				forPositivEmployeeLoop
			);
		ICommandConstruction addEducationLevel =
			new AddEducationLevel(
				premiumValVariable,
				forPositivEmployeeLoop
			);
		ICommandConstruction addTimeSpentLevel =
			new AddTimeSpentLevel(
				premiumValVariable,
				forPositivEmployeeLoop
			);
		ICommandConstruction substractReprimand =
			new SubstractReprimand(
				premiumValVariable,
				forPositivEmployeeLoop
			);
		ICommandConstruction premiumAssigner =
			new PremiumAssigner(
				premiumInfo,
				premiumValVariable,
				forPositivEmployeeLoop
			);
		CorrectCritical correctCritical = new CorrectCritical();
		forPositivEmployeeLoop.addBodyCommands(
			initialisePremiumVal,
			addEducationLevel,
			addTimeSpentLevel,
			substractReprimand,
			premiumAssigner,
			correctCritical
		);
		forPositivEmployeeLoop.extractAgentsFromConstructions();
		forPositivEmployeeLoop.getAgents().add(
			new RegisterServicesAgent(premiumInfo)
		);

		// zarchTODO: [after protocol] ugly
		forPositivEmployeeLoop.setProtocol(
			new INegotiationProtocol__Q_del() {
				@SuppressWarnings("unchecked")
				@Override
				public INegotiation createNegotiation(
					ICommandConstruction construction,
					INegotiationContext context
				) {
					INegotiationFor<Object> forLoop =
						(INegotiationFor<Object>) construction;
					return new UtilityFuncNegotiation(
						ExecuteForNegotiation.newSeparateFor(forLoop)
					);
				}
			}
		);

		forPositivEmployeeLoop.execute(new DefaultNegotiationContext());

		assertEquals(
			2,
			correctCritical.getCriticalEmployeeSet().size()
		);

		assertEquals(
			"W0: 78.77; W1: 60.80; W2: 77.50; W3: 93.82; W4: 109.10; ",
			premiumInfo.toString()
		);
	}

	private static class Employee {
		private String name;
		private double educationLevel;
		private double reprimandLevel;
		private double spentTime;

		private Employee(
			String name,
			double educationLevel,
			double reprimandLevel,
			double spentTime
		) {
			this.name = name;
			this.educationLevel = educationLevel;
			this.reprimandLevel = reprimandLevel;
			this.spentTime = spentTime;
		}

		public double getEducationLevel() {
			return educationLevel;
		}

		public double getReprimandLevel() {
			return reprimandLevel;
		}

		public double getSpentTime() {
			return spentTime;
		}

		@Override
		public String toString() {
			return name;
		}
	}

	private static interface IPremiumsInfo
		extends IService
	{
		public void putPremium(
			Employee employee,
			double premium
		);
		public double getPremium(Employee employee);
	}

	private static class PremiumsInfo
		implements IPremiumsInfo
	{
		private Map<Employee, Double> employee2premium =
			new LinkedHashMap<Employee, Double>();

		@Override
		public void putPremium(
			Employee employee,
			double premium
		) {
			employee2premium.put(employee, premium);
		}

		@Override
		public double getPremium(Employee employee) {
			Double premval = employee2premium.get(employee);
			return premval == null ? 0.0 : premval;
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			DecimalFormat format = new DecimalFormat();
			format.setMinimumFractionDigits(2);
			format.setMaximumFractionDigits(2);
			DecimalFormatSymbols dfs =
				format.getDecimalFormatSymbols();
			dfs.setDecimalSeparator('.');
			format.setDecimalFormatSymbols(dfs);

			for (
				Entry<Employee, Double> entry :
				employee2premium.entrySet()
			) {
				Employee employee = entry.getKey();
				Double value = entry.getValue();
				String valueRepr = format.format(value);
				sb.append(employee).append(": ")
					.append(valueRepr).append("; ");
			}
			return sb.toString();
		}
	}

	private static class PremiumValVariable {
		private double value;

		private PremiumValVariable() {
			erase();
		}

		public void erase() {
			value = 0.0;
		}

		public void addVal(double value) {
			this.value += value;
		}

		public void multiplyVal(double mul) {
			value *= mul;
		}

		public double getPremiumValue() {
			return value;
		}
	}

	private abstract static class BasePremiumNegotiationAgent
		extends BaseUtilityFuncAgent
		implements ICommandConstruction
	{
		protected PremiumValVariable premiumValVariable;
		protected INegotiationFuzzyFor<Employee> forLoop;
		protected boolean activeCmd = false;

		protected BasePremiumNegotiationAgent(
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop
		) {
			this.premiumValVariable = premiumValVariable;
			this.forLoop = forLoop;
		}

		@Override
		public void execute(IExecutionContext context) {
		}

		@Override
		public double computeUtility(
			INegotiationContext context
		) {
			return Double.MAX_VALUE;
		}

		@SuppressWarnings("unchecked")
		protected static Employee emploeeFromContext(INegotiationContext context) {
			IExecuteForService<Employee> forServ =
				context.getService(IExecuteForService.class);
			Employee employee = forServ.getCurrentElem();
			return employee;
		}
	}

	private static class CriticalParam {
		public double maxDisutility = -Double.MAX_VALUE;
		public Employee criticalEmployee = null;
		public double premiumDelta = Double.NaN;
	}

	private abstract static class BaseAddPremiumValueAgent
		extends BasePremiumNegotiationAgent
		implements IUtilityFuncNegotiationAgent
	{
		protected double veight = Double.NaN;
		private Object lastCriticalParam = null;

		protected BaseAddPremiumValueAgent(
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop,
			double veight
		) {
			super(premiumValVariable, forLoop);
			this.veight = veight;
		}

		protected abstract double extractParameter(
			Employee employee
		);

		@Override
		public boolean negotiate(INegotiationContext context) {
			Employee employee = emploeeFromContext(context);
			double parameter = extractParameter(employee);
			double deltaPremium = parameter * veight;
			premiumValVariable.addVal(deltaPremium);
			return false;
		}

		@Override
		public double computeUtility(
			INegotiationContext context
		) {
			IFuzzySet<Employee> employeeFuzzySet =
				forLoop.getFuzzyForSet();
			IPremiumsInfo premiumInfo =
				context.getService(IPremiumsInfo.class);
			double sumpremium = 0.0;
			double sumparams = 0.0;
			for (Employee employee : employeeFuzzySet.getElements()) {
				double membership =
					employeeFuzzySet.getMembershipDegree(employee);
				sumparams += extractParameter(employee);
				sumpremium +=
					premiumInfo.getPremium(employee) / membership;
			}

			CriticalParam criticalParam = new CriticalParam();
			double disutility = 0.0;
			for (Employee employee : employeeFuzzySet.getElements()) {
				double membership =
					employeeFuzzySet.getMembershipDegree(employee);
				double parameter = extractParameter(employee);
				double parameterCoef = parameter / sumparams;
				double expectedPremium =
					parameterCoef * sumpremium * membership;
				double premium = premiumInfo.getPremium(employee);
				double addition = Math.abs(expectedPremium - premium);
				if (addition > criticalParam.maxDisutility) {
					criticalParam.maxDisutility = addition;
					criticalParam.criticalEmployee = employee;
					criticalParam.premiumDelta =
						expectedPremium - premium;
				}
				disutility += addition;
			}
			lastCriticalParam = criticalParam;

			return -disutility;
		}

		@Override
		public void addCriticalParams(List<Object> criticalParams) {
			criticalParams.add(lastCriticalParam);
			lastCriticalParam = null;
		}
	}

	private static class CorrectCritical
		extends BaseUtilityFuncAgent
		implements ICommandConstruction
	{
		private List<CriticalParam> criticalParams =
			new ArrayList<CriticalParam>();

		private Set<Employee> criticalEmployeeSet =
			new HashSet<Employee>();

		@Override
		public void onSingleNegotiationStageEnd(
			INegotiationContext context
		) {
			correctCriticalPremium(context);
		}

		@Override
		public double computeUtility(
			INegotiationContext context
		) {
			return Double.MAX_VALUE;
		}

		@Override
		public void execute(IExecutionContext context) {
		}

		public Set<Employee> getCriticalEmployeeSet() {
			return criticalEmployeeSet;
		}

		private void correctCriticalPremium(INegotiationContext context) {
			IUtilityInfoService utilServ =
				context.getService(IUtilityInfoService.class);
			if (utilServ.getCriticalParams().isEmpty()) {
				return;
			}
			assertEquals(
				1,
				utilServ.getCriticalParams().size()
			);
			CriticalParam newCriticalParam =
				(CriticalParam)utilServ.getCriticalParams().get(0);

			criticalEmployeeSet.add(newCriticalParam.criticalEmployee);

			criticalParams.add(newCriticalParam);
			IPremiumsInfo premiumInfo =
				context.getService(IPremiumsInfo.class);
			for (CriticalParam criticalParam : criticalParams) {
				applyCriticalParam(criticalParam, premiumInfo);
			}
		}

		private static void applyCriticalParam(
			CriticalParam criticalParam,
			IPremiumsInfo premiumInfo
		) {
			double delta = criticalParam.premiumDelta;
			double step = delta / 2.0;
			Employee criticalEmployee =
				criticalParam.criticalEmployee;
			double premium =
				premiumInfo.getPremium(criticalEmployee);
			double correctedPremium = premium + step;
			premiumInfo.putPremium(
				criticalEmployee,
				correctedPremium
			);
		}
	}

	private static class InitialisePremiumVal
		extends BasePremiumNegotiationAgent
	{
		protected InitialisePremiumVal(
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop
		) {
			super(premiumValVariable, forLoop);
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			premiumValVariable.erase();
			return false;
		}
	}

	private static class AddEducationLevel
		extends BaseAddPremiumValueAgent
	{
		private static final double VEIGHT = 10.0;

		protected AddEducationLevel(
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop
		) {
			super(premiumValVariable, forLoop, VEIGHT);
		}

		@Override
		protected double extractParameter(
			Employee employee
		) {
			return employee.getEducationLevel();
		}
	}

	private static class AddTimeSpentLevel
		extends BaseAddPremiumValueAgent
	{
		private static final double VEIGHT = 6.0;

		protected AddTimeSpentLevel(
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop
		) {
			super(premiumValVariable, forLoop, VEIGHT);
		}

		@Override
		protected double extractParameter(Employee employee) {
			return employee.getSpentTime();
		}
	}

	private static class SubstractReprimand
		extends BaseAddPremiumValueAgent
	{
		protected SubstractReprimand(
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop
		) {
			super(premiumValVariable, forLoop, Double.NaN);
		}

		@Override
		protected double extractParameter(Employee employee) {
			double reprimandLevel = employee.getReprimandLevel();
			return 1.0 / reprimandLevel;
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			Employee employee = emploeeFromContext(context);
			double reprimandLevel = employee.getReprimandLevel();
			if (reprimandLevel > EPS) {
				premiumValVariable.multiplyVal(
					extractParameter(employee)
				);
			}
			return false;
		}
	}

	private static class PremiumAssigner
		extends BasePremiumNegotiationAgent
	{
		private PremiumsInfo premiumsInfo;

		protected PremiumAssigner(
			PremiumsInfo premiumsInfo,
			PremiumValVariable premiumValVariable,
			INegotiationFuzzyFor<Employee> forLoop
		) {
			super(premiumValVariable, forLoop);
			this.premiumsInfo = premiumsInfo;
		}

		@Override
		public boolean negotiate(INegotiationContext context) {
			Employee employee = emploeeFromContext(context);
			double membership =
				ZArchNegotiationUtils.getCurrentMembershipDegree(context);
			double premiumValue =
				premiumValVariable.getPremiumValue();
			double premium2assign =
				premiumValue * membership;
			premiumsInfo.putPremium(employee, premium2assign);
			return false;
		}
	}
}
